import struct
import collections
class ELF:
    def __init__(self,data):
        self.data = data
        # 入口地址
        self.entry_start_address = None
        # 程序头表数目
        self.number_of_program_header_table = None
        # 节表数目
        self.number_of_section_table = None
        # 程序头表在文件中的offset
        self.program_header_offset_in_file = None
        # 节表头在文件中的offset
        self.section_header_offset_in_file = None
        # string_table_index
        self.string_table_index = None
        # string_table_address
        self.string_table_start = None
        # dysym_offset
        self.dysym_offset = None
        # sym_table_size
        self.sym_table_size = None
        self.headerStruct()
        self.programHeaderTableStruct()
        self.sectionHeaderTable()
        self.symbolTable()

    def headerStruct(self):
        # I + 12 + 2 * 2 + 5 * 4 + 6 * 2 = 52
        format_code = "IssssssssssssHHIIIIIHHHHHH"
        self.elf_header_struct = struct.Struct("<"+format_code) # 2H5I6H
        self.elf_header = collections.namedtuple("ELFHeader",(
            "magic",
            "ei_class",
            "ei_data",
            "ei_version",
            "ei_osabi",
            "ei_abiversion",

            "ei_pad0",
            "ei_pad1",
            "ei_pad2",
            "ei_pad3",
            "ei_pad4",
            "ei_pad5",

            "ei_nident_SIZE",

            "e_type",
            "e_machin",

            "e_version",
            "e_entry_start_address",
            "e_phoff_program_header_offset_in_file",
            "e_shoff_section_header_offset_in_file",
            "e_flags",
            "e_ehsize_elf_header_size",
            "e_phentsize_program_header_entry_size_in_file",
            "e_phnum_number_of_program_header_entries",
            "e_shentsize_section_header_entry_size",
            "e_shnum_number_of_section_header_entries",
            "e_shtrndx_string_table_index"
        ))
        elf_header = self.elf_header(*self.elf_header_struct.unpack_from(self.data,0))
        self.number_of_program_header_table = elf_header.e_phnum_number_of_program_header_entries
        self.number_of_section_table = elf_header.e_shnum_number_of_section_header_entries
        self.program_header_offset_in_file = elf_header.e_phoff_program_header_offset_in_file
        self.section_header_offset_in_file = elf_header.e_shoff_section_header_offset_in_file
        self.entry_start_address = elf_header.e_entry_start_address
        self.string_table_index = elf_header.e_shtrndx_string_table_index

    def programHeaderTableStruct(self):
        format_code = "IIIIIIII"
        self.elf_program_header_struct = struct.Struct("<"+format_code)
        self.elf_program_header = collections.namedtuple("ELFProgramHeader",(
            "p_type",
            "p_offset",
            "p_vaddr",
            "p_paddr",
            "p_filesz",
            "p_memsz",
            "p_flags",
            "p_align"
        ))
        offset = self.program_header_offset_in_file
        for i in range(self.number_of_program_header_table):
            elf_program_header = self.elf_program_header(*self.elf_program_header_struct.unpack_from(self.data,offset + i * 32))

    def sectionHeaderTable(self):
        '''
        .rel.text:定位的地方在.text段内，以offset指定具体要定位位置。在链接时候由链接器完成。
        .rel.dyn:重定位的地方在.got段内，主要是针对外部数据变量符号。不支持延迟重定位（lazy)，通常so文件执行时就在.init段中进行重定位操作、
        .rel.plt:重定位的地方在.got.plt段内，主要是针对外部函数符号。一般是函数首次被调用时候重定位。
        .plt:Procedure Linkage Table，过程链接表。所有对外部函数的调用都经过PLT再到GOT的一个调用过程。
        .got:Global Offset Table,全局偏移表，存放着调用外部函数的实际地址（第一次存放的是PLT中的指令，PLT执行完之后吧计算得到的实际值在存到GOT中）
        .got.plt:ELF将GOT表拆分成两个表，.got表和.got.plt，前者用来保存全部变量引用的地址，后者用来保存函数引用的地址。
        '''
        format_code = "IIIIIIIIII"
        self.elf_section_header_struct = struct.Struct("<"+format_code)
        self.elf_section_header = collections.namedtuple("ELFSectionHeader",(
            "sh_name",     # 给出节区名称。是节区头部字符串表节区域
            "sh_type",     # 为节区的内容和语义进行分类
            "sh_flags",
            "sh_addr",
            "sh_offset",   # 节区第一个字节与字节头之间的偏移
            "sh_size",     # 给出节区的长度（字节数）
            "sh_link",
            "sh_info",
            "sh_addralign",# 对齐约束
            "sh_entsize"
        ))
        offset = self.section_header_offset_in_file
        elf_section_header = self.elf_section_header(*self.elf_section_header_struct.unpack_from(self.data,offset + 40 * self.string_table_index))
        self.string_table_start = elf_section_header.sh_offset
        # 索引为0(SHN_UNDEF)的节区头部也是存在的
        for i in range(self.number_of_section_table):
            elf_section_header = self.elf_section_header(*self.elf_section_header_struct.unpack_from(self.data,offset))
            j = self.string_table_start+elf_section_header.sh_name
            while self.data[j:j+1].hex() != "00":
                j+=1
            sec_name = self.data[self.string_table_start+elf_section_header.sh_name:j].decode()
            print("表名 : {} \t\t offset : {}".format(sec_name,hex(elf_section_header.sh_offset)))
            if sec_name == ".dynsym":
                self.dysym_offset = elf_section_header.sh_offset
                self.sym_table_size = elf_section_header.sh_entsize
            offset += 40

    def symbolTable(self):
        format_code = "IIIssH"
        self.elf_symble_table_struct = struct.Struct("<" + format_code)
        self.elf_symble_table = collections.namedtuple("ELFSymbleTable", (
            "st_name",   # 包含目标文件符号字符串的索引，其中包含符号名的字符串表示。如果该值非0，则它表示了给出符号名的字符串索引，否则符号表项没有名称
            "st_value",  # 此成员给出相关联的符号的取值。依赖于具体的上下文，它可能是一个绝对值、一个地址等。
            "st_size",   # 很多符号具有相关的尺寸大小。例如一个数据对象的大小是对象中包含的字节数。如果符号没有大小或者大小未知，则此成员为 0
            "st_info",   # 此成员给出符号的类型和绑定属性。
            "st_other",  # 该成员当前包含 0 ， 其含义没有定义
            "st_shndx"   # 每个符号表项都以和其他节区间的关系的方式给出定义。
        ))
        offset = self.dysym_offset
        while offset < len(self.data):
            elf_symble_table = self.elf_symble_table(*self.elf_symble_table_struct.unpack_from(self.data,offset))
            offset += self.sym_table_size

    def bigEndian(self,data):
        data = hex(data).split("0x")[1]
        trdata = ""
        for i in range(0,len(data),2):
            trdata = data[i]+data[i+1] + trdata
        trdata = "0x"+trdata
        return trdata
